/* Link the libraries code-wise. */
#ifdef _MSC_VER
#	pragma comment(lib, "OpenGL32.lib")
#	pragma comment(lib, "GLu32.lib")

#	pragma comment(lib, "SDL.lib")
#	pragma comment(lib, "SDLmain.lib")
#	pragma comment(lib, "SDL_image.lib")
#endif //_MSC_VER

#include <Windows.h>
#include <math.h>
#include <stdio.h>
#include <stdarg.h>
#include <string>
#include <cmath>
#include <ctime> 
#include <cstdlib>
#include <iostream>

#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <SDL/SDL_image.h>

#include <GL/gl.h>
#include <GL/glut.h>
#include <GL/glu.h>
#include <GL/glaux.h>

using namespace std;

#define PI 3.141592653589793

HDC			hDC=NULL;		// Private GDI Device Context
HGLRC		hRC=NULL;		// Permanent Rendering Context
HWND		hWnd=NULL;		// Holds Our Window Handle
HINSTANCE	hInstance;		// Holds The Instance Of The Application

GLuint	base;				// Base Display List For The Font Set

unsigned Textures[6];
unsigned BoxList(0);					//Added!

/* These will define the player's position and view angle. */
double X(0.0), Y(0.0), Z(0.0);
double ViewAngleHor(0.0), ViewAngleVer(0.0);
double playerX(0.0), playerZ(0.0);
int playerRow(0), playerCol(0);
double row4X(0.0);
double row7X(0.0);
double row8X(0.0);
const int PLAYER_KEY_NUM = 4;
double movingRate(1.0);
const int GAME_BOARD_COLUMN = 8;
const int GAME_BOARD_ROW = 12;
int gameBoard[GAME_BOARD_ROW][GAME_BOARD_COLUMN];


/*
 * DegreeToRadian
 *	Converts a specified amount of degrees to radians.
 */
inline double DegreeToRadian(double degrees)
{
	return (degrees / 180.f * PI);
}

/*
 * GrabTexObjFromFile
 *	This function will use SDL to load the specified image, create an OpenGL
 *	texture object from it and return the texture object number.
 */
GLuint GrabTexObjFromFile(const std::string& fileName)
{
	/* Use SDL_image to load the PNG image. */
	SDL_Surface *Image = IMG_Load(fileName.c_str());
	
	/* Image doesn't exist or failed loading? Return 0. */
	if(!Image)
		return 0;

	unsigned Object(0);

	/* Generate one texture (we're creating only one). */
	glGenTextures(1, &Object);

	/* Set that texture as current. */
	glBindTexture(GL_TEXTURE_2D, Object);

	/* You can use these values to specify mipmaps if you want to, such as 'GL_LINEAR_MIPMAP_LINEAR'. */
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	/* We're setting textures to be repeated here. */
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); //NEW!
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); //NEW!

	/* Create the actual texture object. */
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Image->w, Image->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, Image->pixels);

	/* Free the surface, we are finished with it. */
	SDL_FreeSurface(Image);

	return Object;
}

/*
 *	CompileLists
 *		Compiles the display lists used by our application.
 */
void CompileLists()
{
	/* Let's generate a display list for a box. */
	BoxList = glGenLists(1);
	glNewList(BoxList, GL_COMPILE);

		/*
		 * Render everything as you usually would, without texture binding. We're rendering the box from the
		 * '3D Objects' tutorial here.
		 */
		glBegin(GL_QUADS);
			/* Front */
			glTexCoord2d(0, 0); glVertex3d(400, 125, 0.4);
			glTexCoord2d(1, 0); glVertex3d(750, 125, 0.4);
			glTexCoord2d(1, 1); glVertex3d(750, 475, 0.4);
			glTexCoord2d(0, 1); glVertex3d(400, 475, 0.4);

			/* Left side */
			glTexCoord2d(0, 0); glVertex3d(400, 125, -0.4);
			glTexCoord2d(1, 0); glVertex3d(400, 125, 0.4);
			glTexCoord2d(1, 1); glVertex3d(400, 475, 0.4);
			glTexCoord2d(0, 1); glVertex3d(400, 475, -0.4);

			/* Back */
			glTexCoord2d(0, 0); glVertex3d(750, 125, -0.4);
			glTexCoord2d(1, 0); glVertex3d(400, 125, -0.4);
			glTexCoord2d(1, 1); glVertex3d(400, 475, -0.4);
			glTexCoord2d(0, 1); glVertex3d(750, 475, -0.4);

			/* Right side */
			glTexCoord2d(0, 0); glVertex3d(750, 125, 0.4);
			glTexCoord2d(1, 0); glVertex3d(750, 125, -0.4);
			glTexCoord2d(1, 1); glVertex3d(750, 475, -0.4);
			glTexCoord2d(0, 1); glVertex3d(750, 475, 0.4);

			/* Top */
			glTexCoord2d(0, 0); glVertex3d(400, 125, -0.4);
			glTexCoord2d(1, 0); glVertex3d(750, 125, -0.4);
			glTexCoord2d(1, 1); glVertex3d(750, 125, 0.4);
			glTexCoord2d(0, 1); glVertex3d(400, 125, 0.4);

			/* Bottom */
			glTexCoord2d(0, 0); glVertex3d(400, 475, -0.4);
			glTexCoord2d(1, 0); glVertex3d(750, 475, -0.4);
			glTexCoord2d(1, 1); glVertex3d(750, 475, 0.4);
			glTexCoord2d(0, 1); glVertex3d(400, 475, 0.4);
		glEnd();
	glEndList();
}

/*
 * DrawBoxes
 * This will render the Obstacle Boxes including fixed and autonomous boxes on the floor.
 */
void DrawBoxes()
{
	// Row 0 - boundary
	// Row 1 - player start row
	// Row 2 - empty
	// Row 3 - 4 fixed boxes
	glPushMatrix();						
		glScaled(0.5, 0.4, 0.5);
		glBindTexture(GL_TEXTURE_2D, Textures[4]);
		glTranslated(-750, 750, -3.4);
		glCallList(BoxList);
		glTranslated(350, 0, 0);
		glCallList(BoxList);
		glTranslated(350, 0, 0);
		glCallList(BoxList);
		glTranslated(1050, 0, 0);
		glCallList(BoxList);	
	glPopMatrix();
	// Row 4 - 1 moving box
	glPushMatrix();						
		glScaled(0.5, 0.4, 0.5);
		glBindTexture(GL_TEXTURE_2D, Textures[5]);
		glTranslated(-750 + row4X, 750, -2.6);
		glCallList(BoxList);
	glPopMatrix();
	// Row 5 - 3 fixed boxes
	glPushMatrix();						
		glScaled(0.5, 0.4, 0.5);
		glBindTexture(GL_TEXTURE_2D, Textures[4]);
		glTranslated(-50, 750, -1.8);
		glCallList(BoxList);
		glTranslated(350, 0, 0);
		glCallList(BoxList);
		glTranslated(700, 0, 0);
		glCallList(BoxList);
	glPopMatrix();
	// Row 6 - 1 fixed box
	glPushMatrix();						
		glScaled(0.5, 0.4, 0.5);
		glBindTexture(GL_TEXTURE_2D, Textures[4]);
		glTranslated(650, 750, -1);
		glCallList(BoxList);
	glPopMatrix();
	// Row 7 - 2 fixed boxes and 1 moving box
	glPushMatrix();						
		glScaled(0.5, 0.4, 0.5);
		glBindTexture(GL_TEXTURE_2D, Textures[4]);
		glTranslated(-750, 750, -0.2);
		glCallList(BoxList);
		glTranslated(350, 0, 0);
		glCallList(BoxList);
		glBindTexture(GL_TEXTURE_2D, Textures[5]);
		glTranslated(1400 + row7X, 0, 0);
		glCallList(BoxList);
	glPopMatrix();
	// Row 8 - 1 moving box
	glPushMatrix();						
		glScaled(0.5, 0.4, 0.5);
		glBindTexture(GL_TEXTURE_2D, Textures[5]);
		glTranslated(1000 + row8X, 750, 0.6);
		glCallList(BoxList);
	glPopMatrix();
	// Row 9 - 3 fixed boxes
	glPushMatrix();						
		glScaled(0.5, 0.4, 0.5);
		glBindTexture(GL_TEXTURE_2D, Textures[4]);
		glTranslated(-50, 750, 1.4);
		glCallList(BoxList);
		glTranslated(350, 0, 0);
		glCallList(BoxList);
		glTranslated(350, 0, 0);
		glCallList(BoxList);
	glPopMatrix();
	// Row 10 - destination row
	// Row 11 - boundary
}

/*
 * DrawPlayer
 * This will render a box which represents the player
 */
void DrawPlayer()
{
	/* Shrink box size */
	glScaled(0.5, 0.4, 0.5);

	/* Bind textures */
	glBindTexture(GL_TEXTURE_2D, Textures[3]);

	glTranslated(-400 + playerX, 750, -5 + playerZ);
	
	/* glCallList is all that is really needed to execute the display list. */
	glCallList(BoxList);
}

/*
 * DrawRoom
 *	This will render the entire scene (in other words, draw the room).
 */
void DrawRoom()
{
	/* You also could do this at front by using the SDL surface's values or in an array. */
	static float WallTexWidth(0.f);
	static float WallTexHeight(0.f);

	static float FloorTexWidth(0.f);
	static float FloorTexHeight(0.f);

	static bool Once(false);

	/* Perform this check only once. */
	if(!Once)
	{
		/* Bind the wall texture. */
		glBindTexture(GL_TEXTURE_2D, Textures[0]);

		/* Retrieve the width and height of the current texture (can also be done up front with SDL and saved somewhere). */
		glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &WallTexWidth);
		glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &WallTexHeight);

		/* Bind the floor texture. */
		glBindTexture(GL_TEXTURE_2D, Textures[1]);

		/* Retrieve the width and height of the current texture (can also be done up front with SDL and saved somewhere). */
		glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &FloorTexWidth);
		glGetTexLevelParameterfv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &FloorTexHeight);

		Once = true;
	}

	glPushMatrix();

		/* Move the world and rotate the view. */
		glRotated(ViewAngleVer, 1, 0, 0);
		glRotated(ViewAngleHor, 0, 1, 0);

		glTranslated(-X, -Y, -Z);

		/* Set the coordinate system. */
		glOrtho(0, 800, 600, 0, -1, 1);

		/* Draw walls. */
		glBindTexture(GL_TEXTURE_2D, Textures[0]);
		glBegin(GL_QUADS);
			/* Wall in front of you when the app starts. */
			glTexCoord2f(0, 0);
			glVertex3d(-200,   0, 4.0);

			glTexCoord2f(1200.f / WallTexWidth, 0);
			glVertex3d(1000,   0, 4.0);

			glTexCoord2f(1200.f / WallTexWidth, 400.f / WallTexHeight);
			glVertex3d(1000, 500, 4.0);

			glTexCoord2f(0, 400.f / WallTexHeight);
			glVertex3d(-200, 500, 4.0);

			/* Wall left of you. */
			glTexCoord2f(0, 0);
			glVertex3d(-200,   0,-4.0);

			glTexCoord2f(1200.f / WallTexWidth, 0);
			glVertex3d(-200,   0, 4.0);

			glTexCoord2f(1200.f / WallTexWidth, 400.f / WallTexHeight);
			glVertex3d(-200, 500, 4.0);

			glTexCoord2f(0, 400.f / WallTexHeight);
			glVertex3d(-200, 500,-4.0);

			/* Wall right of you. */
			glTexCoord2f(0, 0);
			glVertex3d(1000, 0, 4.0);

			glTexCoord2f(1200.f / WallTexWidth, 0);
			glVertex3d(1000, 0,-4.0);

			glTexCoord2f(1200.f / WallTexWidth, 400.f / WallTexHeight);
			glVertex3d(1000, 500,-4.0);

			glTexCoord2f(0, 400.f / WallTexHeight);
			glVertex3d(1000, 500, 4.0);

			/* Wall behind you (you won't be able to see this just yet, but you will later). */
			glTexCoord2f(0, 0);
			glVertex3d(1000, 0,-4.0);

			glTexCoord2f(1200.f / WallTexWidth, 0);
			glVertex3d(-200, 0,-4.0);

			glTexCoord2f(1200.f / WallTexWidth, 400.f / WallTexHeight);
			glVertex3d(-200, 500,-4.0);

			glTexCoord2f(0, 400.f / WallTexHeight);
			glVertex3d(1000, 500,-4.0);
		glEnd();

		/* Draw the floor */
		glBindTexture(GL_TEXTURE_2D, Textures[1]);
		glBegin(GL_QUADS);
			/* floor */
			//left rectangle 6*10
			glTexCoord2f(0.f, 0.f);
			glVertex3d(-200, 500, 4.0);

			glTexCoord2f(6.f, 0.f);
			glVertex3d(600, 500, 4.0);

			glTexCoord2f(6.f, 10.f);
			glVertex3d(600, 500,-4.0);

			glTexCoord2f(0.f, 10.f);
			glVertex3d(-200, 500,-4.0);
			
			// middle rectangle 1*9
			glTexCoord2f(0.f, 0.f);
			glVertex3d(600, 500, 3.6);

			glTexCoord2f(1.f, 0.f);
			glVertex3d(800, 500, 3.6);

			glTexCoord2f(1.f, 9.f);
			glVertex3d(800, 500,-4.0);

			glTexCoord2f(0.f, 9.f);
			glVertex3d(600, 500,-4.0);

			// right rectangle 1*10
			glTexCoord2f(0.f, 0.f);
			glVertex3d(800, 500, 4.0);

			glTexCoord2f(1.f, 0.f);
			glVertex3d(1000, 500, 4.0);

			glTexCoord2f(1.f, 10.f);
			glVertex3d(1000, 500, -4.0);

			glTexCoord2f(0.f, 10.f);
			glVertex3d(800, 500, -4.0);

		glEnd();
			
		/* draw the ceiling. */
//		glBindTexture(GL_TEXTURE_2D, Textures[2]);
//		glBegin(GL_QUADS);
//
//			glTexCoord2f(0, 0);
//			glVertex3d(-200, 0, 4.0);
//
//			glTexCoord2f(6, 0);
//			glVertex3d(1000, 0, 4.0);
//
//			glTexCoord2f(6, 10);
//			glVertex3d(1000, 0,-4.0);
//
//			glTexCoord2f(0, 10);
//			glVertex3d(-200, 0,-4.0);
//		glEnd();

		/* target markers 1*1 */
		glBindTexture(GL_TEXTURE_2D, Textures[5]);
		glBegin(GL_QUADS);

			glTexCoord2f(0, 0);
			glVertex3d(600, 500, 4.0);

			glTexCoord2f(1, 0);
			glVertex3d(800, 500, 4.0);

			glTexCoord2f(1, 1);
			glVertex3d(800, 500, 3.6);

			glTexCoord2f(0, 1);
			glVertex3d(600, 500, 3.6);

		glEnd();

		/* Now we're going to render some boxes using display lists. */
		glPushMatrix();
			/* Let's make it a bit smaller... */
			glScaled(0.5, 0.4, 0.5);

			/* Can't bind textures while generating a display list, but we can give it texture coordinates and bind it now. */
			glBindTexture(GL_TEXTURE_2D, Textures[2]);

			/*
			 * Because display lists have preset coordinates, we'll need to translate it to move it around. Note that we're
			 * moving the small version of the cube around, not the big version (because we scaled *before* translating).
			 */
			glTranslated(-700, 750, 6);

			/*
			 * Let's draw a whole lot of boxes. Note that because we're not pushing and popping matrices, translations
			 * and changes will 'accumulate' and add to the previous translation.
			 */
			for(short i(0); i < 12; ++i)
			{
				glTranslated(350, 0, 0);

				/* These make sure that every once in a while, a new row is started. */
				if(i == 5)		glTranslated(-1575, -350, 0);
				if(i == 9)		glTranslated(-1225, -350, 0);

				/*
				 * glCallList is all that is really needed to execute the display list. Remember to try the 'K' button
				 * to turn on wireframe mode, with these extra polygons, it looks pretty neat!
				 */
				glCallList(BoxList);
			}
			
		glPopMatrix();

		/* From here we begin to create Sokoban game */
		/* Draw Sokoban boxes */
		DrawBoxes();

		// Draw player
		glPushMatrix();

			DrawPlayer();
		
		glPopMatrix();

	glPopMatrix();
}

/*
 * ifCollidedWhenGoingRight
 * This function detects if an autonomous catches up with the Player Box when it is moving from left to right. 
 */
bool ifCollidedWhenGoingRight(int rowX) 
{
	return ((rowX + 250) >= (-400 + playerX)) && ((rowX + 250) <= (-400 + playerX + 350));
}

/*
 * ifCollidedWhenGoingRight
 * This function detects if an autonomous catches up with the Player Box when it is moving from right to left. 
 */
bool ifCollidedWhenGoingLeft(int rowX)
{
	return ((rowX + 100) >= (-400 + playerX)) && ((rowX + 100) <= (-400 + playerX + 350));
}

/**
 * calculateMovingBoxPosition
 * Calculate the cell postion of the moving box
 */
void calculateMovingBoxPosition(double movingX, int rowIndex)
{
	int colIndex = (movingX + 750)/350 + 1;
	// Handle Row 7
	if (rowIndex == 7) 
	{
		for (int col = 3; col < GAME_BOARD_COLUMN - 1; col ++)
		{
			if (col == colIndex)
				gameBoard[rowIndex][col] = 1;
			else
				gameBoard[rowIndex][col] = 0;
		}
	}
	else // Handle Row 4 and Row 8
	{
		for (int col = 1; col < GAME_BOARD_COLUMN - 1; col ++)
		{
			if (col == colIndex)
				gameBoard[rowIndex][col] = 1;
			else
				gameBoard[rowIndex][col] = 0;
		}
	}
}

/*
 * ifCollided
 * This function calculates if the next move will cause the Player Box to collide with the Obstacle Boxes or not
 */
bool ifCollided(bool playerKeys[PLAYER_KEY_NUM]) 
{
	bool ifCollided = false;

	// Calculate Row 4 moving box position
	calculateMovingBoxPosition(-750 + row4X, 4);

	// Calculate Row 7 moving box position
	calculateMovingBoxPosition(1000 + row7X, 7);		
		
	// Calculate Row 8 moving box position
	calculateMovingBoxPosition(1000 + row8X, 8);

	if (playerKeys[0]) // W pressed, move towards farend
		if (gameBoard[playerRow + 1][playerCol] != 0) // there is a box in the destination cell
			return ifCollided = true;
	if (playerKeys[1]) // S pressed, move towards nearend
		if (gameBoard[playerRow - 1][playerCol] != 0) // there is a box in the destination cell
			return ifCollided = true;		
	if (playerKeys[2]) // A pressed, move towards left
		if (gameBoard[playerRow][playerCol - 1] != 0) // there is a box in the destination cell
			return ifCollided = true;		
	if (playerKeys[3]) // D pressed, move towards right
		if (gameBoard[playerRow][playerCol + 1] != 0) // there is a box in the destination cell
			return ifCollided = true;	

	return ifCollided;
}

/**
 * getPlayerPosition
 * This function calculates the current position of the Player Box in gameBoard[][]
 */
void getPlayerPosition(int &row, int &col) 
{
	row = (int)((-5.0 + playerZ + 5.8)/0.8 + 0.5);
	col = (-400 + playerX + 750)/350 + 1;	
}

/**
 * generateGameBoardMatrix
 * Generate game board matrix to indicate if there is an Obstacle Box in a position on the floor
 */
void generateGameBoardMatrix() 
{
	// 1 means there is a box in the game board cell. 0 meas no box.
	// Initially, set all boundary cells to be 1 and set all cells inside to be 0.
	for (int row = 0; row < GAME_BOARD_ROW; row ++) 
	{
		for (int col = 0; col < GAME_BOARD_COLUMN; col ++)
		{
			if ((row == 0) || (row == 11) || (col == 0) || (col == 7))
				gameBoard[row][col] = 1;
			else
				gameBoard[row][col] = 0;		
		}
	}

	// Row 0 - boundary
	// Row 1 - player start row
	// Row 2 - empty
	// Row 3 - 4 fixed boxes
	gameBoard[3][1] = 1;
	gameBoard[3][2] = 1;
	gameBoard[3][3] = 1;
	gameBoard[3][6] = 1;
	// Row 4 - 1 moving box
	// Row 5 - 3 fixed boxes
	gameBoard[5][3] = 1;
	gameBoard[5][4] = 1;
	gameBoard[5][6] = 1;
	// Row 6 - 1 fixed box
	gameBoard[6][5] = 1;
	// Row 7 - 2 fixed boxees and 1 moving box
	gameBoard[7][1] = 1;
	gameBoard[7][2] = 1;
	// Row 8 - 1 moving box
	// Row 9 - 3 fixed boxes
	gameBoard[9][3] = 1;
	gameBoard[9][4] = 1;
	gameBoard[9][5] = 1;
	// Row 10 - destination row
	// Row 11 - boundary
}

/**
 * check if the application is in fullscreen mode
 * return 1 if is, 0 otherwise
 */
int isFullScreen(SDL_Surface *surface)
{
	if(surface->flags & SDL_FULLSCREEN)
		return 1;
	return 0;
}

/**
 * toggle the application in fullscreen mode and windowed mode
 */
int SDL_ToggleFS(SDL_Surface *surface)
{
	Uint32 flags = surface->flags;	// get the video surface flags
	if(isFullScreen(surface))
	{
		// switch to windowed mode
		flags &= ~SDL_FULLSCREEN;
		if((surface = SDL_SetVideoMode(surface->w, surface->h, surface->format->BitsPerPixel, flags)) == NULL)
			return 0;
		DrawRoom();
		return 1;
	}

	// switch to fullscreen mode
    flags = flags|SDL_FULLSCREEN; 
    if ((surface = SDL_SetVideoMode(surface->w, surface->h, surface->format->BitsPerPixel, flags)) == NULL) 
		return 0; 
	DrawRoom();
    return 1; 
}

/**
 * toggle the application in flat shading and smooth shading
 */
void toggleShading()
{
	GLfloat mat[16];
	glGetFloatv(GL_SHADE_MODEL, mat);
	// change the shading mode
	if( mat[0] == 7424)
		glShadeModel(GL_SMOOTH);
	else if( mat[0] == 7425 )
		glShadeModel(GL_FLAT);
	else
		glShadeModel(GL_FLAT);
}

/**
 * popup a message box to ask user whether run in fullscreen mode or in windowed mode
 */
int selectScreenMode()
{
	if(MessageBoxA(NULL, "Would you like to play in Fullscreen mode?","Select Screen Mode", MB_YESNO | MB_ICONQUESTION)==IDYES)
		return SDL_OPENGL | SDL_FULLSCREEN;
	else
		return SDL_OPENGL;
}

/**
 * get user input of difficulty selection
 * from console window
 */
void selectLevel() 
{
	int input = 0;

	do
	{
		cout << "Input your choice of difficulty:\n";
		cout << "1 - Easy\n";
		cout << "2 - Medium\n";
		cout << "3 - Hard\n";
		cout << "Your choice: ";
		cin >> input;
		if(cin.fail())
		{
			cout << "\n\nPlease enter a valid integer from 1, 2, 3\n\n";
			cin.clear();
			cin.ignore();
		}
	}while(input != 1 && input != 2 && input != 3);

	switch(input)
	{
	case 1:
		movingRate = 0.8;
		break;
	case 2:
		movingRate = 1.0;
		break;
	case 3:
		movingRate = 1.2;
		break;
	default:
		movingRate = 1.0;
		break;
	}

}

/**
 * popup a message box indicate that user fail in game
 */
void showFailWin()
{
	MessageBoxA(NULL, "You are catched by the moving box. Game Over...","GAME OVER", MB_OK | MB_ICONWARNING | MB_DEFBUTTON1);
}

/**
 * popup a message box indicate success
 */
void showSuccessWin()
{
	MessageBoxA(NULL, "Congratulations! You made it!","GAME WIN", MB_OK | MB_ICONINFORMATION | MB_DEFBUTTON1);
}

/* application starts from here */
int main(int argc, char **argv)
{
	bool ifRow4BoxGoingRight = true;
	bool ifRow7BoxGoingRight = true;
	bool ifRow8BoxGoingRight = true;
	const int DESTINATION_ROW = 10;
	const int DESTINATION_COLUMN = 5;

	// use console window to get user input of game level
	selectLevel();
	
	generateGameBoardMatrix();

	/* Initialize SDL and set up a window. */
	SDL_Init(SDL_INIT_VIDEO);
	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
	SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
	SDL_WM_SetCaption("3D Moving Box", 0);
	SDL_WM_GrabInput(SDL_GRAB_ON);
	SDL_ShowCursor(SDL_DISABLE);
	int flags = selectScreenMode();
	SDL_Surface *surface = SDL_SetVideoMode(800, 600, 32, flags);

	/* Basic OpenGL initialization, handled in 'The Screen'. */
	/* set material properties and light source */
	GLfloat light_position[] = {0, 1000, 0, 1};
	GLfloat ambient[] = {0.05f, 0.05f, 0.05f, 1.0f};
	GLfloat diffuse[] = {0.8f, 0.8f, 0.8f, 1.0f};
	GLfloat specular[] = {0.6f, 0.6f, 0.6f, 1.0f};

	glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, ambient);
	glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, diffuse);
	glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, specular);

	glLightfv(GL_LIGHT0, GL_POSITION, light_position);
	glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
	glLightfv(GL_LIGHT0, GL_SPECULAR, specular);

	glEnable(GL_LIGHTING);
	glEnable(GL_LIGHT0);

	glClearColor(0.3,0.4,0.8,1);
	glShadeModel(GL_SMOOTH);

	glViewport(0, 0, 800, 600);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluPerspective(80.0, 800.0/600.0, 0.1, 100.0);

	/* We now switch to the modelview matrix. */
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslated(0, 0, -3.5);

	glEnable(GL_DEPTH_TEST);
	
	glDepthFunc(GL_LEQUAL);

	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	/* Enable 2D texturing. */
	glEnable(GL_TEXTURE_2D);

	/* Set up alpha blending. */
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glColor4d(1, 1, 1, 1);

	Textures[0] = GrabTexObjFromFile("Data/Wall.png");
	Textures[1] = GrabTexObjFromFile("Data/floor3.png");
	Textures[2] = GrabTexObjFromFile("Data/Box.png");
	Textures[3] = GrabTexObjFromFile("Data/crate.png");
	Textures[4] = GrabTexObjFromFile("Data/block.png");
	Textures[5] = GrabTexObjFromFile("Data/target.png");

	//Replaced this with a loop that immediately checks the entire array.
	//sizeof(Textures) is the size of the entire array in bytes (unsigned int = 4 bytes)
	//so sizeof(Textures) would give 3 * 4 = 12 bytes, divide this by 4 bytes and you
	//have 3.
	for(unsigned i(0); i < sizeof(Textures) / sizeof(unsigned); ++i)
	{
		if(Textures[i] == 0)
		{
#ifdef _WIN32
		MessageBoxA(0, "Something went seriously wrong!", "Fatal Error!", MB_OK | MB_ICONERROR);
#endif //_WIN32

		return 1;
		}
	}

	/* Compile the display lists. */
	CompileLists();

	SDL_Event event;

	int RelX(0), RelY(0);
	int MovementDelay(SDL_GetTicks());

	bool Wireframe(false);
	bool Keys[4] =
	{
		false, /* Up arrow down? */
		false, /* Down arrow down? */
		false, /* Left arrow down? */
		false  /* Right arrow down? */
	};

	bool playerKeys[PLAYER_KEY_NUM] = 
	{
		false, /* Key W down? */
		false, /* Key S down? */
		false, /* Key A down? */
		false  /* Key D down? */
	};

	/* Application loop. */
	for(;;)
	{
		/* Handle events with SDL. */
		if(SDL_PollEvent(&event))
		{
			if(event.type == SDL_QUIT)
				break;

			/* Mouse events? */
			else if(event.type == SDL_MOUSEMOTION)
			{
				/* Get the relative mouse movement of the mouse (based on CurMouseCoord - PrevMouseCoord). */
				SDL_GetRelativeMouseState(&RelX, &RelY);

				ViewAngleHor += RelX / 4;
				ViewAngleVer += RelY / 4;

				/* Prevent the horizontal angle from going over 360 degrees or below 0 degrees. */
				if(ViewAngleHor >= 360.0)		ViewAngleHor = 0.0;
				else if(ViewAngleHor < 0.0)		ViewAngleHor = 360.0;

				/* Prevent the vertical view from moving too far (comment this out to get a funny effect). */
				if(ViewAngleVer > 60.0)			ViewAngleVer = 60.0; /* 60 degrees is when you're looking down. */
				else if(ViewAngleVer < -60.0)	ViewAngleVer = -60.0; /* This is when you're looking up. */

				/* This delay might seem strange, but it helps smoothing out the mouse if you're experiencing jittering. */
				SDL_Delay(5);
			}

			else if(event.type == SDL_KEYDOWN)
			{
				if(event.key.keysym.sym == SDLK_ESCAPE)
					break;
				if(event.key.keysym.sym == SDLK_k)
					glPolygonMode(GL_FRONT_AND_BACK, ((Wireframe = !Wireframe)? GL_LINE : GL_FILL));
				if(event.key.keysym.sym == SDLK_l)
				{
					toggleShading();
				}
				if(event.key.keysym.sym == SDLK_UP)			Keys[0] = true;
				if(event.key.keysym.sym == SDLK_DOWN)		Keys[1] = true;
				if(event.key.keysym.sym == SDLK_LEFT)		Keys[2] = true;
				if(event.key.keysym.sym == SDLK_RIGHT)		Keys[3] = true;

				if(event.key.keysym.sym == SDLK_w)			playerKeys[0] = true;
				if(event.key.keysym.sym == SDLK_s)			playerKeys[1] = true;
				if(event.key.keysym.sym == SDLK_a)			playerKeys[2] = true;
				if(event.key.keysym.sym == SDLK_d)			playerKeys[3] = true;
			}

			else if(event.type == SDL_KEYUP)
			{
				if(event.key.keysym.sym == SDLK_UP)			Keys[0] = false;
				if(event.key.keysym.sym == SDLK_DOWN)		Keys[1] = false;
				if(event.key.keysym.sym == SDLK_LEFT)		Keys[2] = false;
				if(event.key.keysym.sym == SDLK_RIGHT)		Keys[3] = false;

				if(event.key.keysym.sym == SDLK_w)			playerKeys[0] = false;
				if(event.key.keysym.sym == SDLK_s)			playerKeys[1] = false;
				if(event.key.keysym.sym == SDLK_a)			playerKeys[2] = false;
				if(event.key.keysym.sym == SDLK_d)			playerKeys[3] = false;
			}
		}

		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		glPushMatrix();
			DrawRoom();				
		glPopMatrix();


		/* Move if the keys are pressed, this is explained in the tutorial. */
		if(Keys[0])
		{
			X -= cos(DegreeToRadian(ViewAngleHor + 90.0)) * 0.05;
			Z -= sin(DegreeToRadian(ViewAngleHor + 90.0)) * 0.05;
		}

		if(Keys[1])
		{
			X += cos(DegreeToRadian(ViewAngleHor + 90.0)) * 0.05;
			Z += sin(DegreeToRadian(ViewAngleHor + 90.0)) * 0.05;
		}

		if(Keys[2])
		{
			X += cos(DegreeToRadian(ViewAngleHor + 180.0)) * 0.05;
			Z += sin(DegreeToRadian(ViewAngleHor + 180.0)) * 0.05;
		}

		if(Keys[3])
		{
			X -= cos(DegreeToRadian(ViewAngleHor + 180.0)) * 0.05;
			Z -= sin(DegreeToRadian(ViewAngleHor + 180.0)) * 0.05;
		}

		// Get the player's current position
		getPlayerPosition(playerRow, playerCol);

		/* Move the player if any of the W/S/A/D keys are pressed */
		if (playerKeys[0] && !ifCollided(playerKeys)) // W pressed, move towards farend
			playerZ += 0.8;
		if (playerKeys[1] && !ifCollided(playerKeys)) // S pressed, move towards nearend
			playerZ -= 0.8;
		if (playerKeys[2] && !ifCollided(playerKeys)) // A pressed, move towards left
			playerX -= 350;
		if (playerKeys[3] && !ifCollided(playerKeys)) // D pressed, move towards right
			playerX += 350;			

		/* Coodinate for Row 4 autonomous object*/		
		if (row4X <= 0) 
			ifRow4BoxGoingRight = true;
		if (row4X >= 1750) 
			ifRow4BoxGoingRight = false;	

		if (ifRow4BoxGoingRight)
			row4X += 50 * movingRate;
		else
			row4X -= 50 * movingRate;

		/* Coodinate for Row 7 autonomous object*/		
		if (row7X <= -1050)
			ifRow7BoxGoingRight = true;
		if (row7X >= 0)
			ifRow7BoxGoingRight = false;
		
		if (ifRow7BoxGoingRight)
			row7X += 50 * movingRate;
		else
			row7X -= 50 * movingRate;

		/* Coodinate for Row 8 autonomous object*/		
		if (row8X <= -1750)
			ifRow8BoxGoingRight = true;
		if (row8X >= 0)
			ifRow8BoxGoingRight = false;
		
		if (ifRow8BoxGoingRight)
			row8X += 50 * movingRate;
		else
			row8X -= 50 * movingRate;


		// Check if the moving box of Row 4 catches the player
		if (playerRow == 4)
		{
			if (ifRow4BoxGoingRight) 
			{
				if (ifCollidedWhenGoingRight(-750 + row4X))
				{
					showFailWin();
					break;
				}
			}
			else
			{	
				if (ifCollidedWhenGoingLeft(-750 + row4X))
				{
					showFailWin();
					break;
				}
			}
		}

		// Check if the moving box of Row 7 catches the player
		if (playerRow == 7)
		{
			if (ifRow7BoxGoingRight) 
			{
				if (ifCollidedWhenGoingRight(1000 + row7X))
				{
					showFailWin();
					break;
				}
			}
			else
			{	
				if (ifCollidedWhenGoingLeft(1000 + row7X))
				{
					showFailWin();
					break;
				}
			}
		}

		// Check if the moving box of Row 8 catches the player
		if (playerRow == 8)
		{
			if (ifRow8BoxGoingRight) 
			{
				if (ifCollidedWhenGoingRight(1000 + row7X))
				{
					showFailWin();
					break;
				}
			}
			else
			{	
				if (ifCollidedWhenGoingLeft(1000 + row7X))
				{
					showFailWin();
					break;
				}
			}
		}

		// Check if the player reaches the destination
		if ((playerRow == DESTINATION_ROW) && (playerCol == DESTINATION_COLUMN))
		{
			showSuccessWin();
			break;
		}

		/* Swap the display buffers. */
		SDL_GL_SwapBuffers();
	}

	/* Delete the created textures. */
	glDeleteTextures(3, Textures);		//Changed to 3.
	glDeleteLists(BoxList, 1);

	/* Clean up. */
	SDL_Quit();

	//glutInit(&argc, argv);
	//glutKeyboardFunc(myKeyboard);


	return 0;
}